﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WordNet.Core.ElementContracts;
using WordNet.Core.Extensions;

namespace WordNet.Core
{
    public class Synset:ISynset
    {
        private readonly ISynsetID _id;
        private readonly String _gloss;
        private readonly ILexFile _lexFile;
        private readonly List<IWord> _words;
        private readonly bool _isAdjSat;
        private readonly bool _isAdjHead;
        private readonly List<ISynsetID> _related;
        private readonly Dictionary<IPointer, List<ISynsetID>> _relatedMap;

        public Synset(ISynsetID id, ILexFile lexFile, bool isAdjSat, bool isAdjHead, String gloss,
            List<IWordBuilder> wordBuilders, Dictionary<IPointer, List<ISynsetID>> ids)
        {

            if (id == null)
                throw new ArgumentNullException();
            if (lexFile == null)
                throw new ArgumentNullException();
            if (gloss == null)
                throw new ArgumentNullException();
            if (wordBuilders == null)
                throw new ArgumentNullException();
            if (wordBuilders.Count() == 0)
                throw new ArgumentNullException();
            if (isAdjSat && isAdjHead)
                throw new ArgumentNullException();
            if ((isAdjSat || isAdjHead) && lexFile.Number != 0)
                throw new ArgumentNullException();

            _id = id;
            _lexFile = lexFile;
            _gloss = gloss;
            _isAdjSat = isAdjSat;
            _isAdjHead = isAdjHead;

            // words
            List<IWord> words = new List<IWord>(wordBuilders.Count());
            foreach (IWordBuilder wordBuilder in wordBuilders)
                words.Add(wordBuilder.ToWord(this));
            _words = words;

            HashSet<ISynsetID> hiddenSet = null;
            Dictionary<IPointer, List<ISynsetID>> hiddenMap = null;
            // fill synset map
            if (ids != null)
            {
                hiddenSet = new HashSet<ISynsetID>();
                hiddenMap = new Dictionary<IPointer, List<ISynsetID>>(ids.Count(),new PointerIndexKeyComparator());
                foreach (KeyValuePair<IPointer, List<ISynsetID>> entry in ids)
                {
                    if (entry.Value == null || entry.Value.Count() == 0)
                        continue;
                    hiddenMap.Add(entry.Key, new List<ISynsetID>(entry.Value));
                    foreach (var val in entry.Value)
                        hiddenSet.Add(val);
                }
            }
            _related = (hiddenSet != null && hiddenSet.Count() != 0) ? new List<ISynsetID>(hiddenSet) : new List<ISynsetID>();
            _relatedMap = (hiddenMap != null && hiddenMap.Count() != 0) ? hiddenMap : null;
        }
        
       

        public int Offset
        {
            get { return _id.Offset; }
        }


        public ILexFile LexicalFile
        {
            get { return _lexFile; }
        } 

        public int TypeOfPOS
        {
            get
            {
                POS pos = PartOfSpeech;
                if (pos != POS.GetPartOfSpeech(DbPartOfSpeechType.Adj))
                    return pos.Number;
                return IsAdjectiveSatellite ? 5 : 3;
            }
        }
        
        public string Gloss
        {
            get { return _gloss; }
        }

        public List<IWord> GetWords()
        {
            return _words;
        }

        public IWord GetWord(int wordNumber)
        {
            return _words[wordNumber - 1];
        }

        public bool IsAdjectiveHead
        {
            get { return _isAdjHead; }
        }

        public bool IsAdjectiveSatellite
        {
            get { return _isAdjSat; }
        }

        public Dictionary<IPointer, List<ISynsetID>> GetRelatedMap()
        {
            return _relatedMap;
        }

        public List<ISynsetID> GetRelatedSynsets(IPointer ptr)
        {
            if (_relatedMap == null)
                return null;
            if (!_relatedMap.ContainsKey(ptr))
                return null;

            List<ISynsetID> result = _relatedMap[ptr];
            return result != null ? result : new List<ISynsetID>(); 
        }

        public List<ISynsetID> GetRelatedSynsets()
        {
            return _related;
        }

        public ISynsetID ID
        {
            get { return _id; }
        }

        public override int GetHashCode()
        {
            int PRIME = 31;
            int result = 1;
            result = PRIME * result + _gloss.GetHashCode();
            result = PRIME * result + (_isAdjSat ? 1231 : 1237);
            result = PRIME * result + _id.GetHashCode();
            foreach(var w in _words) 
                result = PRIME * result + w.GetHashCode();
            result = PRIME * result + _relatedMap.GetHashCode();
            return result;
        }
       
        public override bool Equals(Object obj)
        {
            if (Object.ReferenceEquals(obj, null))
                return false;
            if (Object.ReferenceEquals(this, obj))
                return true;

            ISynset other = obj as ISynset;
            if(other == null) 
                return false;
            
            if (!_id.Equals(other.ID))
                return false;
            if (!_words.SequenceEqual(other.GetWords()))
                return false;
            if (!_gloss.Equals(other.Gloss))
                return false;
            if (_isAdjSat != other.IsAdjectiveSatellite)
                return false;
            if (!_relatedMap.Equals(other.GetRelatedMap()))
                return false;
            return true;
        }
        
        public POS PartOfSpeech
        {
            get { return _id.PartOfSpeech; }
        }
    }
}
